﻿using System;
using System.Collections.Generic;
using System.Linq;
using SlamCms.Common;

namespace SlamCms.Data
{
	public static class TagTreeExtensions
	{
		/// <summary>
		/// Updates the number of items for each Tag node based on the list of content items passed by param
		/// </summary>
        /// <param name="tagTree">The TagTree for which you want to update the tag's counts</param>
		/// <param name="contentItems">The content items used to update the counts for the tags. Each one must have its Tags property loaded.</param>
		/// <returns></returns>
		public static TagTree UpdateContentItemCount(this TagTree tagTree, IEnumerable<ContentItem> contentItems)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				n.ContentItemCount = contentItems.Count(ci => ci.Tags.Count(t => t.Identifier.In(n.Identifiers)) == n.Identifiers.Length);
			});

			return tagTree;
		}

        /// <summary>
        /// Updates the number of items for each Tag node based on a content query passed by param
        /// </summary>
        /// <param name="tagTree">The TagTree for which you want to update the tag's counts.</param>
        /// <param name="queryBuilder">The QueryBuilder you want to use for filtering the counts.</param>
        /// <param name="topLevelOnly">Indicates if the counts must be calculated for top level tags only. It will improve the performace of calculation.</param>
        /// <returns></returns>
        public static TagTree UpdateContentItemCount(this TagTree tagTree, QueryBuilder queryBuilder, bool topLevelOnly = false)
        {
            if (tagTree.Root == null)
                return tagTree;

            var tagHierarchyCounts = TagTreeCountsQueryResult.Get(queryBuilder.Context, queryBuilder.Parameters, tagTree.TagTreeId, topLevelOnly);

            ApplyToTagTreeNode(tagTree.Root, n =>
            {
                if (n.IsTag() && (!topLevelOnly || n.IsTopLevelTag()))
                {
                    var positionId = Convert.ToInt32(n.Id.Substring(1));
                    int contentItemCount;
                    if (tagHierarchyCounts.Results.TryGetValue(positionId, out contentItemCount))
                        n.ContentItemCount = contentItemCount;
                }
            });

            return tagTree;
        }

	    /// <summary>
        /// This method is not using the cache pipeline and ignoring several required filters like Status. Please use a QueryBuilder to define the parameters.
		/// </summary>
        /// <param name="tagTree">The TagTree for which you want to update the tag's counts.</param>
		/// <returns></returns>
		[Obsolete("This method is not using the cache pipeline and ignoring several required filters like Status. Please use a QueryBuilder to define the parameters.")]
        public static TagTree UpdateContentItemCount(this TagTree tagTree, string[] zoneIds, string[] contentTypeIds, string[] taggedWithAny)
		{
			if (tagTree.Root == null)
				return tagTree;

			var tagHierarchyCounts = tagTree.Context.Database.RetrieveTagTreeContentItemCount(tagTree.TagTreeId, zoneIds, contentTypeIds, taggedWithAny);

			// for now it's just the top level tags
			foreach (var tagCategoryNode in tagTree.Root.Children)
			{
				foreach (var tagNode in tagCategoryNode.Children)
				{
					var positionId = Convert.ToInt32(tagNode.Id.Substring(1));
					int contentItemCount;
					if (tagHierarchyCounts.TryGetValue(positionId, out contentItemCount))
						tagNode.ContentItemCount = contentItemCount;
				}
			}

			return tagTree;
		}

        /// <summary>
        /// Removes the tags from the tree with having the count of items in zero.
        /// </summary>
        /// <param name="tagTree">The TagTree.</param>
        /// <returns></returns>
		public static TagTree RemoveTagsWithNoItems(this TagTree tagTree)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				var removeList = new List<TagTreeNode>();
				foreach (var childNode in n.Children)
				{
					if (childNode.Type == TagTreeNodeType.Tag && childNode.ContentItemCount == 0)
						removeList.Add(childNode);
				}

				foreach (var node in removeList)
					n.Children.Remove(node);
			});

			return tagTree;
		}

        /// <summary>
        /// Removes the tag categories with no children tags.
        /// </summary>
        /// <param name="tagTree">The TagTree.</param>
        /// <returns></returns>
		public static TagTree RemoveTagCategoriesWithNoTags(this TagTree tagTree)
		{
			var tagCategoriesToRemove = tagTree.Root.Children.Where(n => !n.Children.Any()).ToList();
			foreach (var tagCategory in tagCategoriesToRemove)
				tagTree.Root.Children.Remove(tagCategory);
			return tagTree;
		}

        /// <summary>
        /// Removes the tag categories passed by param.
        /// </summary>
        /// <param name="tagTree">The TagTree.</param>
        /// <param name="tagCategoryIds">The Id(s) of the tag categories to remove</param>
        /// <returns></returns>
		public static TagTree RemoveTagCategories(this TagTree tagTree, params string[] tagCategoryIds)
		{
			var tagCategoriesToRemove = tagTree.Root.Children.Where(n => n.TagCategory != null && n.TagCategory.TagCategoryId.In(tagCategoryIds)).ToList();
			foreach (var tagCategory in tagCategoriesToRemove)
				tagTree.Root.Children.Remove(tagCategory);

			return tagTree;
		}

        /// <summary>
        /// Removes the tags marked to be hidden for TagMenus, but excluding the tags passed as parameter
        /// </summary>
        /// <param name="tagTree">The TagTree.</param>
        /// <param name="filteredTags">Tags to exclude from the removal</param>
        /// <returns></returns>
		public static TagTree RemoveHiddenTagsForTagMenu(this TagTree tagTree, string[] filteredTags = null)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				if (n.Children != null)
				{
					var removeList = new List<TagTreeNode>();

					foreach (var childNode in n.Children)
					{
						if (childNode.IsTag() &&
							childNode.Tag != null &&
							(childNode.Tag.HideInTagMenu ?? false) &&
							(filteredTags == null || !filteredTags.Any(t => t == childNode.Tag.Identifier)))
							removeList.Add(childNode);
					}

					foreach (var node in removeList)
						n.Children.Remove(node);
				}
			});

			return tagTree;
		}

        /// <summary>
        /// Removes the tags with a particular count value
        /// </summary>
        /// <param name="tagTree">The TagTree.</param>
        /// <param name="count">The count value</param>
        /// <returns></returns>
		public static TagTree RemoveTagsWithCount(this TagTree tagTree, int count)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				var removeList = new List<TagTreeNode>();
				foreach (var childNode in n.Children)
				{
					if (childNode.IsTag() && !childNode.IsSelected && childNode.ContentItemCount == count)
						removeList.Add(childNode);
				}

				foreach (var node in removeList)
					n.Children.Remove(node);
			});

			return tagTree;
		}

		public static TagTree SetRestrictAccessForTags(this TagTree tagTree, string[] tagIdentifiers, bool restrictAccess)
		{
			if (tagIdentifiers.Length > 0)
			{
				ApplyToTagTreeNode(tagTree.Root, n =>
				{
					if (n != null)
					{
						foreach (var childNode in n.Children)
						{
							if (childNode.Type == TagTreeNodeType.Tag && childNode.Tag != null && !String.IsNullOrEmpty(childNode.Tag.Identifier) &&
								(childNode.Tag.Identifier.In(tagIdentifiers) || (n.Tag != null && n.Tag.RestrictAccess == restrictAccess && restrictAccess != childNode.Tag.RestrictAccess)))
								childNode.Tag.RestrictAccess = restrictAccess;
						}
					}
				});
			}

			return tagTree;
		}

		public static TagTree FilterTagCategories(this TagTree tagTree, params string[] tagCategoryId)
		{
			var tagCategoriesToRemove = tagTree.Root.Children.Where(n => !n.TagCategory.TagCategoryId.In(tagCategoryId)).ToList();
			foreach (var tagCategory in tagCategoriesToRemove)
				tagTree.Root.Children.Remove(tagCategory);
			return tagTree;
		}

		public static TagTree FilterTagScope(this TagTree tagTree, string contentType)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				var removeList = new List<TagTreeNode>();
				foreach (var childNode in n.Children)
				{
					if (childNode.Type == TagTreeNodeType.Tag && childNode.Tag.Scopes != null && childNode.Tag.Scopes.Count > 0 && !childNode.Tag.Scopes.Any(c => c.Title.Equals(contentType, StringComparison.OrdinalIgnoreCase)))
						removeList.Add(childNode);
				}

				foreach (var node in removeList)
					n.Children.Remove(node);
			});

			return tagTree;
		}

		public static TagTree Localize(this TagTree tagTree, string culture)
		{
			var tagCultures = tagTree.Context.Cache.Get<IDictionary<string, TagCulture>>(Constants.Cache.TagCulture);
			var tagCategoryCultures = tagTree.Context.Cache.Get<IDictionary<string, TagCategoryCulture>>(Constants.Cache.TagCategoryCulture);

			if (tagCultures == null || tagCategoryCultures == null)
				return tagTree;

			TagCulture tagCulture;
			TagCategoryCulture tagCategoryCulture;

			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				if (n.Type == TagTreeNodeType.Tag && n.Tag != null)
				{
					var key = n.Tag.TagId + "_" + culture;

					if (tagCultures.TryGetValue(key, out tagCulture))
					{
						n.Text = tagCulture.DisplayName ?? n.Text;
						n.Tag.DisplayName = tagCulture.DisplayName ?? n.Tag.DisplayName;
						n.Tag.Description = tagCulture.Description ?? n.Tag.Description;
						n.Tag.PageContent = tagCulture.PageContent ?? n.Tag.PageContent;
					}
				}
				else if (n.Type == TagTreeNodeType.TagCategory && n.TagCategory != null)
				{
					var key = n.TagCategory.TagCategoryId + "_" + culture;

					if (tagCategoryCultures.TryGetValue(key, out tagCategoryCulture))
					{
						n.Text = tagCategoryCulture.Title ?? n.Text;
						n.TagCategory.Title = tagCategoryCulture.Title ?? n.TagCategory.Title;
					}
				}
			});

			return tagTree;
		}

		public static TagTree OrderAlphabetically(this TagTree tagTree)
		{
			ApplyToTagTreeNode(tagTree.Root, n =>
			{
				if (n.Children.Any())
				{
					n.Children = n.Children.OrderBy(x => x.Text).ToList();
				}
			});

			return tagTree;
		}

		public static void ApplyToTagTreeNode(TagTreeNode node, Action<TagTreeNode> action)
		{
			action(node);

			foreach (var childNode in node.Children)
				ApplyToTagTreeNode(childNode, action);
		}
	}
}
